package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticalStatisticsMapper;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.service.FreemarkerService;
import com.heima.common.exception.CustomException;
import com.heima.common.redis.CacheService;
import com.heima.model.common.comment.dtos.CommentManagePageDto;
import com.heima.model.common.dtos.*;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.common.pojos.article.ApArticle;
import com.heima.model.common.pojos.article.ApArticleConfig;
import com.heima.model.common.pojos.article.ApArticleContent;
import com.heima.model.common.vo.article.ApArticleVo;
import com.heima.utils.thread.ApThreadLocalUtil;
import com.heima.utils.thread.WmThreadLocalUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;
    @Autowired
    private ApArticleConfigMapper apArticleConfigMapper;
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private FreemarkerService freemarkerService;
    @Autowired
    private CacheService cacheService;
    @Autowired
    private ApArticalStatisticsMapper apArticalStatisticsMapper;

    //点赞
    private static final String LIKES_BEHAVIOR = "like";
    //阅读
    private static final String READ_BEHAVIOR = "read";
    //不喜欢
    private static final String UN_LIKES_BEHAVIOR = "unlike";
    //收藏
    private static final String COLLECTIONS_BEHAVIOR = "collection";
    //关注
    private static final String FOLLOW_BEHAVIOR = "follow";

    /**
     * 加载文章信息
     *
     * @param homeDto 查询条件
     * @param type    1：加载更多 2：加载更新
     * @return
     */
    @Override
    public ResponseResult load(ArticleHomeDto homeDto, int type) {
        //1. 参数校验
        if (homeDto == null || (type != 1 && type != 2)) {
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID);
        }

        //2. 数据处理
        if (homeDto.getMaxBehotTime() == null) homeDto.setMaxBehotTime(new Date());
        if (homeDto.getMinBehotTime() == null) homeDto.setMinBehotTime(new Date());
        if (homeDto.getSize() < 1 || homeDto.getSize() > 50) homeDto.setSize(10);

        //3. 调用mapper查询对应数据
        List<ApArticle> res = apArticleMapper.load(homeDto, type);

        //4. 封装结果返回
        return ResponseResult.okResult(res);
    }

    /**
     * 保存或修改文章信息
     *
     * @param articleDto
     * @return
     */
    @Override
    public ResponseResult saveOrUpdateArticle(ArticleDto articleDto) {
        //1. 同步自媒体文章信息
        if (articleDto.getId() == null) {
            //保存
            //1. 保存文章基本信息：ap_article
            ApArticle apArticle = new ApArticle();
            BeanUtils.copyProperties(articleDto, apArticle);
            apArticleMapper.insert(apArticle);
            articleDto.setId(apArticle.getId());

            //2. 创建文章默认配置：ap_article_config
            ApArticleConfig config = new ApArticleConfig(apArticle.getId());
            apArticleConfigMapper.insert(config);

            //3. 保存文章内容信息：ap_article_content
            ApArticleContent content = new ApArticleContent();
            content.setArticleId(apArticle.getId());
            content.setContent(articleDto.getContent());
            apArticleContentMapper.insert(content);
        } else {
            //修改
            //1. 修改文章基本信息：ap_article
            ApArticle apArticle = new ApArticle();
            BeanUtils.copyProperties(articleDto, apArticle);
            apArticleMapper.updateById(apArticle);

            //2. 修改文章内容信息：ap_article_content
            ApArticleContent content = new ApArticleContent();
            content.setArticleId(apArticle.getId());
            content.setContent(articleDto.getContent());
            //update ap_article_content set ? ,? where article_id = ?
            apArticleContentMapper.update(content,
                    Wrappers.<ApArticleContent>lambdaUpdate().eq(ApArticleContent::getArticleId, content.getArticleId()));
        }

        //2. 采用异步调用，提升当前接口的性能，生成文章详情页： freemarker + minio
        freemarkerService.generateDetailHtml(articleDto);


        //将文章id返回
        return ResponseResult.okResult(String.valueOf(articleDto.getId()));
    }

    /**
     * 文章上下架
     * <p>
     * is_down: 0: 上架， 1：下架
     *
     * @param map enable 0：下架， 1：上架
     */
    @Override
    public void downOrUp(Map map) {
        boolean isDown = true;            // 假设要下架

        if ((int) map.get("enable") == 1) {
            isDown = false;
        }
        ApArticleConfig config = new ApArticleConfig();
        //config.setArticleId((long) map.get("article_id"));
        //update ap_article_config set is_down = ? where article_id = ?
        apArticleConfigMapper.update(config, Wrappers.<ApArticleConfig>lambdaUpdate()
                .set(ApArticleConfig::getIsDown, isDown)
                .eq(ApArticleConfig::getArticleId, config.getArticleId()));

    }

    @Override
    public ResponseResult getArticleInfo(Long entryId) {
        ApArticle apArticle = apArticleMapper.selectOne(Wrappers.lambdaQuery(ApArticle.class).eq(ApArticle::getId, entryId));
        ApArticleConfig config = apArticleConfigMapper.selectOne(Wrappers.lambdaQuery(ApArticleConfig.class).eq(ApArticleConfig::getArticleId, apArticle.getId()));
        apArticle.setIsComment(config.getIsComment());
        return ResponseResult.okResult(apArticle);
    }

    /**
     * 用户收藏文章
     *
     * @param behaviorDto
     * @return
     */
    @Override
    public ResponseResult collectionBehavior(CollectionBehaviorDto behaviorDto, String userId) {
        // 1. 参数校验
        if (behaviorDto == null || behaviorDto.getEntryId() == null || behaviorDto.getType() == null) {
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID);
        }

        // 2. 准备数据
        Long entryId = behaviorDto.getEntryId();
        String key = userId + "_" + entryId;

        // 3. 从 Redis 查询数据
        String value = null;
        try {
            value = (String) cacheService.hGet(COLLECTIONS_BEHAVIOR, key);
        } catch (Exception e) {
            return ResponseResult.errorResult(AppHttpCodeEnum.ERROR);
        }

        CollectionBehaviorDto currentDto;
        if (value == null) {
            // 如果不存在记录，直接存入
            currentDto = new CollectionBehaviorDto();
            currentDto.setEntryId(entryId);
            currentDto.setOperation(behaviorDto.getOperation());
            cacheService.hPut(COLLECTIONS_BEHAVIOR, key, JSON.toJSONString(currentDto));
        } else {
            // 如果存在记录，更新 operation 为前端传入的值
            currentDto = JSON.parseObject(value, CollectionBehaviorDto.class);
            currentDto.setOperation(behaviorDto.getOperation());
            cacheService.hPut(COLLECTIONS_BEHAVIOR, key, JSON.toJSONString(currentDto));
        }

        // 4. 修改 ap_article 表
        ApArticle article = apArticleMapper.selectById(entryId);
        if (article == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        if (article.getCollection() == null) {
            article.setCollection(0);
        }

        // 5. 执行收藏/取消收藏操作
        if (behaviorDto.getOperation() == 0) { // 收藏
            article.setCollection(article.getCollection() + 1);
        } else if (behaviorDto.getOperation() == 1) { // 取消收藏
            article.setCollection(article.getCollection() - 1);
            cacheService.removeHashField(COLLECTIONS_BEHAVIOR, key);
        }

        apArticleMapper.updateById(article);

        // 6. 返回结果
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    /**
     * 加载文章行为-数据回显
     *
     * @param infoDto
     * @param userId
     * @return
     */
    @Override
    public ResponseResult loadArticleBehavior(ArticleInfoDto infoDto, String userId) {
        if (infoDto == null || infoDto.getArticleId() == null) {
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID);
        }

        Long articleId = infoDto.getArticleId();
        String likeKey = userId + "_" + articleId;
        String unlikeKey = userId + "_" + articleId;
        String collectionKey = userId + "_" + articleId;

        // 获取文章的作者ID
        ApArticle article = apArticleMapper.selectById(articleId);
        if (article == null) {
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        String followKey = userId + "_" + article.getAuthorId(); // 使用作者ID生成关注键

        // 检查用户的行为状态
        boolean isLike = cacheService.hExists(LIKES_BEHAVIOR, likeKey);
        boolean isUnlike = cacheService.hExists(UN_LIKES_BEHAVIOR, unlikeKey);
        boolean isCollection = cacheService.hExists(COLLECTIONS_BEHAVIOR, collectionKey);
        boolean isfollow = cacheService.hExists(FOLLOW_BEHAVIOR, followKey);

        // 构建回显数据
        Map<String, Object> result = new HashMap<>();
        result.put("islike", isLike);
        result.put("isunlike", isUnlike);
        result.put("iscollection", isCollection);
        result.put("isfollow", isfollow);

        return ResponseResult.okResult(result);
    }

    /**
     * 分页查询当前作者的文章数据
     *
     * @param beginDate
     * @param endDate
     * @param page
     * @param size
     * @return
     */
    @Override
    public PageResponseResult newPage(Date beginDate, Date endDate, Integer page, Integer size, Integer id) {
        Page<ApArticle> p = new Page<>(page, size);
        apArticleMapper.selectPage(p, Wrappers.lambdaQuery(ApArticle.class)
                .eq(ApArticle::getAuthorId, id)
                .between(ApArticle::getPublishTime, beginDate, endDate)
                .orderByDesc(ApArticle::getViews));
        PageResponseResult prr = new PageResponseResult((int) p.getCurrent(), (int) p.getSize(), (int) p.getTotal());
        List<ApArticle> records = p.getRecords();
        for (ApArticle record : records) {
            if (record.getLikes() == null)
                record.setLikes(0);
            if (record.getCollection() == null)
                record.setCollection(0);
            if (record.getComment() == null)
                record.setComment(0);
            if (record.getViews() == null)
                record.setViews(0);
        }
        prr.setData(records);
        return prr;
    }

    /**
     * 更改文章评论状态
     *
     * @param statusDto
     * @return
     */
    @Override
    public ResponseResult updateArticleCommentStatus(CommentUpdateStatusDto statusDto) {
        //1.更改文章详情表的状态
        ApArticleConfig apArticleConfig = new ApArticleConfig();
        apArticleConfig.setArticleId(Long.valueOf(statusDto.getArticleId()));
        apArticleConfigMapper.update(apArticleConfig, Wrappers.lambdaUpdate(ApArticleConfig.class)
                .eq(ApArticleConfig::getArticleId, apArticleConfig.getArticleId())
                .set(ApArticleConfig::getIsComment, statusDto.getOperation()));
        if (statusDto.getOperation() == 0) {
            //清空评论数量comment==0
            ApArticle apArticle = new ApArticle();
            apArticle.setId(Long.valueOf(statusDto.getArticleId()));
            apArticleMapper.update(apArticle, Wrappers.lambdaUpdate(ApArticle.class)
                    .eq(ApArticle::getId, apArticle.getId())
                    .set(ApArticle::getComment, 0));
        }
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    /**
     * 查询符合条件的文章
     *
     * @param commentDto
     * @return
     */
    @Override
    public PageResponseResult findList(CommentManagePageDto commentDto) {
        Page<ApArticle> page = new Page<>(commentDto.getPage(), commentDto.getSize());
        Wrapper<ApArticle> queryWrapper = Wrappers.lambdaQuery(ApArticle.class)
                .eq(ApArticle::getAuthorId, commentDto.getAuthorId())
                .ge(ApArticle::getCreatedTime, commentDto.getBeginDate())
                .le(ApArticle::getCreatedTime, commentDto.getEndDate())
                .orderByDesc(ApArticle::getCreatedTime);
        Page<ApArticle> articlePage = page(page, queryWrapper);
        List<ApArticle> apArticleList = articlePage.getRecords();
        apArticleList.forEach(apArticle -> {
            ApArticleConfig config = apArticleConfigMapper.selectOne(Wrappers.lambdaQuery(ApArticleConfig.class).eq(ApArticleConfig::getArticleId, apArticle.getId()));
            apArticle.setIsComment(config.getIsComment());
        });
        PageResponseResult result = new PageResponseResult((int) articlePage.getCurrent(), (int) articlePage.getSize(), (int) articlePage.getTotal());
        result.setData(articlePage.getRecords());
        return result;
    }


    //判断收藏类型
    private boolean isType(int type) {
        // 检查类型是否为0/1，这里假定合法类型为0和1
        return type == 0 || type == 1;

    }

    /**
     * 点赞/取消点赞
     *
     * @param map
     */
    @Override
    public void like(Map map) {
        // 从数据库加载文章
        ApArticle apArticle = apArticleMapper.selectById((Long) map.get("articleId"));

        // 检查文章是否存在
        if (apArticle == null) {
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        // 设置新的点赞数
        int newLikes = (Integer) map.get("likes");
        apArticle.setLikes(newLikes);

        // 更新文章的点赞数
        apArticleMapper.updateById(apArticle);
    }

    /**
     * 用户阅读
     *
     * @param map
     */
    @Override
    public void read(Map map) {
        // 从数据库加载文章
        ApArticle apArticle = apArticleMapper.selectById((Long) map.get("articleId"));

        // 检查文章是否存在
        if (apArticle == null) {
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        // 设置新的阅读数
        int newReads = (Integer) map.get("views");
        apArticle.setViews(newReads);

        // 更新文章的阅读数
        apArticleMapper.updateById(apArticle);
    }

    /**
     * 同步评论数量
     *
     * @param map
     */
    @Override
    public void comment(Map map) {
        ApArticle apArticle = apArticleMapper.selectById((Serializable) map.get("articleId"));

        //判断文章是否存在
        if (apArticle == null) {
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        //设置新的评论数量
        int newComments = (int) map.get("comment");
        apArticle.setComment(apArticle.getComment() == null ? 0 : newComments + apArticle.getComment());

        //更新新的文章评论数量
        apArticleMapper.updateById(apArticle);
    }

    /**
     * 定时计算热点文章
     */
    @Override
    public void computeHotArticle() {
        //1.得出近五天的文章数据
        LocalDate minusDays = LocalDate.now().minusDays(5);
        LocalDateTime fiveDayAgo = LocalDateTime.of(minusDays, LocalTime.MIN);
        List<ApArticle> articles = apArticleMapper.load5Days(fiveDayAgo);
        //2.给文章算分
        List<ApArticleVo> apArticleVos;
        if (articles != null && articles.size() > 0) {
            apArticleVos = computeArticleScore(articles);
        } else {
            return;
        }
        //3.计算出每个频道的热点数据存入redis（取30条）
        //3.1获取所有频道
        List<Integer> channelIds = apArticleVos.stream().map(ApArticle::getChannelId).distinct().collect(Collectors.toList());
        //3.2每个频道的热点数据存入redis（取30条）
        channelIds.stream().forEach(channelId -> {
            List<ApArticleVo> voList = apArticleVos.stream().filter(apArticleVo -> apArticleVo.getChannelId().equals(channelId))
                    .sorted((o1, o2) -> o2.getScore() - o1.getScore())
                    .limit(30)
                    .collect(Collectors.toList());
            //存入redis
            cacheService.set("hot_article" + channelId, JSONArray.toJSONString(voList));
        });

        //4.计算出推荐频道的热点数据存入redis（取30条）
        List<ApArticleVo> voList = apArticleVos.stream().sorted((o1, o2) -> o2.getScore() - o1.getScore())
                .limit(30)
                .collect(Collectors.toList());
        //存入redis
        cacheService.set("hot_article__all__", JSONArray.toJSONString(voList));
    }

    /**
     * 首页加载热点文章
     *
     * @param homeDto
     * @param loadtypeLoadIndex
     * @return
     */
    @Override
    public ResponseResult loadHotArticle(ArticleHomeDto homeDto, int loadtypeLoadIndex) {
        //1.先查redis返回热点文章
        String htoArticleJsonStr = cacheService.get("hot_article" + homeDto.getTag());
        if (StringUtils.isNotEmpty(htoArticleJsonStr)) {
            return ResponseResult.okResult(JSONArray.parseArray(htoArticleJsonStr, ApArticleVo.class));
        }
        //2.没有数据的就按照原逻辑查询数据库
        return load(homeDto, loadtypeLoadIndex);
    }

    //给文章算分
    private List<ApArticleVo> computeArticleScore(List<ApArticle> articles) {
        ArrayList<ApArticleVo> list = new ArrayList<>();
        for (ApArticle article : articles) {
            int score = 0;
            //1.阅读权重1分
            Integer views = article.getViews();
            if (views != null && views > 0) {
                score += views;
            }
            //2.点赞权重3分
            Integer likes = article.getLikes();
            if (likes != null && likes > 0) {
                score += (likes * 3);
            }
            //3.评论权重5分
            Integer comment = article.getComment();
            if (comment != null && comment > 0) {
                score += (comment * 5);
            }
            //4.收藏权重8分
            Integer collection = article.getCollection();
            if (collection != null && collection > 0) {
                score += (collection * 8);
            }
            ApArticleVo apArticleVo = new ApArticleVo();
            BeanUtils.copyProperties(article, apArticleVo);
            apArticleVo.setScore(score);
            list.add(apArticleVo);
        }
        return list;
    }

}
